 /*******************************************************************************
  * Copyright (c) 2005 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.internal.components.framework;

 import java.util.ArrayList ;
 import java.util.Collection ;
 import java.util.HashMap ;
 import java.util.HashSet ;
 import java.util.Iterator ;
 import java.util.List ;
 import java.util.Map ;

 import org.eclipse.ui.internal.components.Assert;
 import org.eclipse.ui.internal.components.DependencyOnlyFactory;
 import org.eclipse.ui.internal.components.util.InstanceToComponentFactoryAdapter;
 import org.eclipse.ui.internal.components.util.InstanceToServiceFactoryAdapter;
 import org.eclipse.ui.internal.components.util.ServiceProviderToServiceFactoryAdapter;

 /**
  * Default implementation of <code>ServiceFactory</code> that is useful for expressing
  * hardcoded dependency rules. A <code>FactoryMap</code> contains a map of
  * component factories and an ordered list of other service factories to delegate to. It
  * resolves dependencies like this:
  *
  * <ol>
  * <li>Check if the type is being explicitly excluded.</li>
  * <li>Check if it there is a specific mapping for the dependency.</li>
  * <li>Run through the list of other service factories to delegate to. The first factory
  * that can handle the dependency will be used. When delegating, all possible types
  * are delegated to the target object.</li>
  * <li>Return null</li>
  * </ol>
  *
  * <p>Note that this is only a convenience class. Any methods that take a service factory as input
  * argument should generally take an <code>AbstractServiceFactory</code> rather than depending
  * on this concrete class.</p>
  *
  * <p>Class keys must match exactly. When a service is looked up by class, the factory will
  * <em>not</em> look for mappings to other compatible types if an exact match is not found.</p>
  *
  * <p>As a convenience, all of the setters on this object return <i>this</i>,
  * allowing them to be chained Smalltalk-style. For example: </p>
  *
  * <code>
  * // Chained initialization
  * Container myContainer = new Container(new FactoryMap()
  * .mapInstance(Dog.class, myDog)
  * .mapInstance(Cat.class, myCat));
  *
  * // Equivalent non-chained initialization
  * FactoryMap context = new FactoryMap();
  * context.mapInstance(Dog.class, myDog);
  * context.mapInstance(Cat.class, myCat);
  * Container myContainer = new Container(context);
  * </code>
  *
  * <p>Not intended to be subclassed by clients.</p>
  *
  * <p>EXPERIMENTAL: The components framework is currently under active development. All
  * aspects of this class including its existence, name, and public interface are likely
  * to change during the development of Eclipse 3.1</p>
  *
  * @since 3.1
  */
 public final class FactoryMap extends ServiceFactory {
     
     /**
      * Mixed-type Map of Object keys onto IComponentInfo (null if empty)
      */
     private Map adapters = null;
     
     private List exclusions = null;
     
     /**
      * List of IContainerContext, or null if empty.
      */
     private List parentContexts = null;
     
     /**
      * List of ContainerInfo, or null if empty
      */
     private List registeredContainers = null;
      
     /**
      * Create an empty ContainerContext that does not recognize any types.
      */
     public FactoryMap() {
     }
     
     /**
      * Blocks the given type from being returned by this context. Subsequent calls to
      * getComponentInfo will return null when asked for this interface, even if there is
      * an instance or parent context that can provide the component. This is not the same
      * as unmapping the type, which simply undoes the effects of a previous mapping and
      * may still fall through to an object registered through add*. This overrides any
      * previous mapping for this interface, and does not affect its subtypes or supertypes.
      *
      * @param interface_ interface to exclude
      * @return this
      */
     public FactoryMap mapExclusion(Object interface_) {
         Assert.isNotNull(interface_);
         if (exclusions == null) {
             exclusions = new ArrayList ();
         }
         exclusions.add(interface_);
         internalAddMapping(interface_, null);
         return this;
     }
     
     /**
      * Maps the given interface to the given factory. The factory must be able
      * to create objects that are assignable to the interface. This will override the
      * effects of any previous mapping for this interface, but does not affect its
      * subtypes or supertypes.
      *
      * @param interface_ interface to implement
      * @param adapter a factory that can create objects of the given type
      * @return this
      */
     public FactoryMap map(Object interface_, ComponentFactory adapter) {
         Assert.isNotNull(adapter);
         Assert.isNotNull(interface_);
         
         internalAddMapping(interface_, adapter);
         return this;
     }
     
     /**
      * Maps the given interface to the given context. Whenever this context is asked
      * for a dependency of the given type, it will delegate to the given context.
      * This will override the effects of any previous mapping for this interface,
      * but does not affect its subtypes or supertypes.
      *
      * @param interfaceType interface to map
      * @param context context to delegate to
      * @return this
      */
     public FactoryMap map(Object interfaceType, ServiceFactory context) {
         Assert.isNotNull(interfaceType);
         Assert.isNotNull(context);
         
         internalAddMapping(interfaceType, context);
         
         // Inherit all of the dependencies from the given factory.
 add(new DependencyOnlyFactory(context));
         return this;
     }
         
     /**
      * Maps the given interface type to the given object instance. Whenever the context
      * is asked for a dependency of the given type, it will return the given object.
      * This will override the effects of any previous mapping for this interface
      * but does not affect its subtypes or supertypes.
      *
      * @param interfaceType interface type to map
      * @param component component instance that either implements the interface or supplies
      * an adapter to the interface
      * @return this
      */
     public FactoryMap mapInstance(Object interfaceType, Object component) {
         Assert.isNotNull(interfaceType);
         Assert.isNotNull(component);
         
         if (interfaceType instanceof Class ) {
             Class c = (Class )interfaceType;
             
             Assert.isTrue(c.isInstance(component));
         }
         
         return map(interfaceType, new InstanceToComponentFactoryAdapter(component));
     }
     
     public FactoryMap addInstance(Object instance) {
         return add(new InstanceToServiceFactoryAdapter(instance));
     }
     
     /**
      * Causes the receiver to delegate to the given context whenever it can't find a dependency.
      *
      * <p>Dependencies registered through add* are processed after specific mappings registered
      * through map*. If two add* calls can satisfy the same dependency, the one that was added
      * first takes precidence.</p>
      *
      * @param context newly added context
      * @return this
      */
     public FactoryMap add(ServiceFactory context) {
         Assert.isNotNull(context);
         internalAddInstance(context);
         
         return this;
     }
     
     public FactoryMap add(IServiceProvider toAdd) {
         Assert.isNotNull(toAdd);
         internalAddInstance(new ServiceProviderToServiceFactoryAdapter(toAdd));
         
         return this;
     }
     
     /* (non-Javadoc)
      * @see org.eclipse.core.component.IComponentArguments#getComponentInstance(java.lang.Class)
      */
     ComponentHandle getInstance(Object type, IServiceProvider availableDependencies) throws ComponentException {
         if (parentContexts == null) {
             return null;
         }
         
         for (Iterator iter = parentContexts.iterator(); iter.hasNext();) {
             ServiceFactory context = (ServiceFactory) iter.next();
             
             ComponentHandle result = context.createHandle(type, availableDependencies);
             if (result != null) {
                 return result;
             }
         }
         return null;
     }

     /* (non-Javadoc)
      * @see org.eclipse.core.component.IComponentContext#get(java.lang.Class)
      */
     public ComponentHandle createHandle(Object key, IServiceProvider availableDependencies) throws ComponentException {
         if (adapters != null) {
             if (adapters.containsKey(key)) {
                 Object target = adapters.get(key);
                 if (target == null) {
                     return null;
                 }
                 
                 if (target instanceof ComponentFactory) {
                     ComponentFactory factory = (ComponentFactory)target;
                     return factory.createHandle(availableDependencies);
                 } else if (target instanceof ServiceFactory) {
                     ServiceFactory targetContext = (ServiceFactory)target;
                     
                     ComponentHandle handle = targetContext.createHandle(key, availableDependencies);
                     if (handle != null) {
                         return handle;
                     }
                 }
             }
         }
         
         // Look for inherited implementations
 return getInstance(key, availableDependencies);
     }

     private void internalAddInstance(ServiceFactory component) {
         if (parentContexts == null) {
             parentContexts = new ArrayList ();
         }
         
         if (!parentContexts.contains(component)) {
             parentContexts.add(component);
         }
     }
     
     private void internalRemoveInstance(ServiceFactory component) {
         if (parentContexts == null) {
             return;
         }
         
         parentContexts.remove(component);
         if (parentContexts.isEmpty()) {
             parentContexts = null;
         }
     }

     private void internalAddMapping(Object interface_, Object toMap) {
         if (adapters == null) {
             adapters = new HashMap ();
         }
         
         adapters.put(interface_, toMap);
     }
     
     /* (non-Javadoc)
      * @see org.eclipse.core.components.IComponentContext#hasKey(java.lang.Object)
      */
     private boolean hasKey(Object componentKey, ServiceFactory toSkip) {
         if (adapters != null) {
             if (adapters.containsKey(componentKey)) {
                 return (adapters.get(componentKey) != null);
             }
         }

         if (parentContexts != null) {
             for (Iterator iter = parentContexts.iterator(); iter.hasNext();) {
                 ServiceFactory context = (ServiceFactory) iter.next();
                 
                 if (context == toSkip) {
                     continue;
                 }
                 
                 if (context.hasService(componentKey)) {
                     return true;
                 }
             }
         }
         
         return false;
     }
     
     /* (non-Javadoc)
      * @see org.eclipse.core.components.IComponentContext#getMissingDependencies()
      */
     public Collection getMissingDependencies() {
         
         HashSet result = new HashSet ();
         
         if (parentContexts != null) {
             for (Iterator iter = parentContexts.iterator(); iter.hasNext();) {
                 ServiceFactory next = (ServiceFactory) iter.next();
                 
                 Collection inheritedDeps = next.getMissingDependencies();
                 
                 for (Iterator iterator = inheritedDeps.iterator(); iterator
                         .hasNext();) {
                     Object dep = (Object ) iterator.next();
                     
                     if (!hasKey(dep, next)) {
                         result.add(dep);
                     }
                 }
             }
         }
         
         if (exclusions != null) {
             result.addAll(exclusions);
         }
         
         return result;
     }

     /* (non-Javadoc)
      * @see org.eclipse.core.components.IComponentContext#hasKey(java.lang.Object)
      */
     public boolean hasService(Object componentKey) {
         return hasKey(componentKey, null);
     }
 }

